// Package network provides network optimization features for on-demand loading
package network

import (
	"container/heap"
	"sync"
	"time"
)

// DownloadQueue manages download queue with priority support
type DownloadQueue struct {
	items    *downloadHeap
	mu       sync.RWMutex
	cond     *sync.Cond
	closed   bool
}

// DownloadItem represents an item in the download queue
type DownloadItem struct {
	download *Download
	priority float64
	addedAt  time.Time
	index    int
}

// downloadHeap implements heap.Interface for priority queue
type downloadHeap []*DownloadItem

func (h downloadHeap) Len() int { return len(h) }

func (h downloadHeap) Less(i, j int) bool {
	// Higher priority first, then earlier added time
	if h[i].priority != h[j].priority {
		return h[i].priority > h[j].priority
	}
	return h[i].addedAt.Before(h[j].addedAt)
}

func (h downloadHeap) Swap(i, j int) {
	h[i], h[j] = h[j], h[i]
	h[i].index = i
	h[j].index = j
}

func (h *downloadHeap) Push(x interface{}) {
	n := len(*h)
	item := x.(*DownloadItem)
	item.index = n
	*h = append(*h, item)
}

func (h *downloadHeap) Pop() interface{} {
	old := *h
	n := len(old)
	item := old[n-1]
	old[n-1] = nil
	item.index = -1
	*h = old[0 : n-1]
	return item
}

// NewDownloadQueue creates a new download queue
func NewDownloadQueue() *DownloadQueue {
	dq := &DownloadQueue{
		items: &downloadHeap{},
	}
	dq.cond = sync.NewCond(&dq.mu)
	heap.Init(dq.items)
	return dq
}

// Add adds a download to the queue
func (dq *DownloadQueue) Add(download *Download) {
	dq.mu.Lock()
	defer dq.mu.Unlock()

	if dq.closed {
		return
	}

	item := &DownloadItem{
		download: download,
		priority: 1.0, // Default priority
		addedAt:  time.Now(),
	}

	heap.Push(dq.items, item)
	dq.cond.Signal()
}

// AddWithPriority adds a download to the queue with specific priority
func (dq *DownloadQueue) AddWithPriority(download *Download, priority float64) {
	dq.mu.Lock()
	defer dq.mu.Unlock()

	if dq.closed {
		return
	}

	item := &DownloadItem{
		download: download,
		priority: priority,
		addedAt:  time.Now(),
	}

	heap.Push(dq.items, item)
	dq.cond.Signal()
}

// Get retrieves the next download from the queue (blocking)
func (dq *DownloadQueue) Get() *Download {
	dq.mu.Lock()
	defer dq.mu.Unlock()

	for {
		if dq.closed {
			return nil
		}

		if dq.items.Len() > 0 {
			item := heap.Pop(dq.items).(*DownloadItem)
			return item.download
		}

		// Wait for new items
		dq.cond.Wait()
	}
}

// GetNonBlocking retrieves the next download from the queue (non-blocking)
func (dq *DownloadQueue) GetNonBlocking() *Download {
	dq.mu.Lock()
	defer dq.mu.Unlock()

	if dq.closed || dq.items.Len() == 0 {
		return nil
	}

	item := heap.Pop(dq.items).(*DownloadItem)
	return item.download
}

// Peek returns the next download without removing it
func (dq *DownloadQueue) Peek() *Download {
	dq.mu.Lock()
	defer dq.mu.Unlock()

	if dq.items.Len() == 0 {
		return nil
	}

	return (*dq.items)[0].download
}

// Size returns the number of items in the queue
func (dq *DownloadQueue) Size() int {
	dq.mu.RLock()
	defer dq.mu.RUnlock()
	return dq.items.Len()
}

// IsEmpty returns true if the queue is empty
func (dq *DownloadQueue) IsEmpty() bool {
	dq.mu.RLock()
	defer dq.mu.RUnlock()
	return dq.items.Len() == 0
}

// Close closes the queue
func (dq *DownloadQueue) Close() {
	dq.mu.Lock()
	defer dq.mu.Unlock()

	dq.closed = true
	dq.cond.Broadcast()
}

// PriorityDownloadQueue extends DownloadQueue with intelligent priority management
type PriorityDownloadQueue struct {
	*DownloadQueue
	stats *QueueStats
	mu    sync.RWMutex
}

// QueueStats contains queue statistics
type QueueStats struct {
	TotalAdded      int64
	TotalProcessed  int64
	HighPriority    int64
	NormalPriority  int64
	LowPriority     int64
	AverageWaitTime time.Duration
	LastUpdated     time.Time
}

// NewPriorityDownloadQueue creates a new priority download queue
func NewPriorityDownloadQueue() *PriorityDownloadQueue {
	return &PriorityDownloadQueue{
		DownloadQueue: NewDownloadQueue(),
		stats:         &QueueStats{},
	}
}

// AddHighPriority adds a high priority download
func (pdq *PriorityDownloadQueue) AddHighPriority(download *Download) {
	pdq.DownloadQueue.AddWithPriority(download, 10.0)
	pdq.updateStats(func(stats *QueueStats) {
		stats.TotalAdded++
		stats.HighPriority++
	})
}

// AddNormalPriority adds a normal priority download
func (pdq *PriorityDownloadQueue) AddNormalPriority(download *Download) {
	pdq.DownloadQueue.AddWithPriority(download, 1.0)
	pdq.updateStats(func(stats *QueueStats) {
		stats.TotalAdded++
		stats.NormalPriority++
	})
}

// AddLowPriority adds a low priority download
func (pdq *PriorityDownloadQueue) AddLowPriority(download *Download) {
	pdq.DownloadQueue.AddWithPriority(download, 0.1)
	pdq.updateStats(func(stats *QueueStats) {
		stats.TotalAdded++
		stats.LowPriority++
	})
}

// GetStats returns queue statistics
func (pdq *PriorityDownloadQueue) GetStats() QueueStats {
	pdq.mu.RLock()
	defer pdq.mu.RUnlock()
	return *pdq.stats
}

// updateStats updates queue statistics
func (pdq *PriorityDownloadQueue) updateStats(update func(*QueueStats)) {
	pdq.mu.Lock()
	defer pdq.mu.Unlock()
	update(pdq.stats)
	pdq.stats.LastUpdated = time.Now()
}

// SmartDownloadQueue provides intelligent queue management with prefetch and batch processing
type SmartDownloadQueue struct {
	*PriorityDownloadQueue
	prefetchQueue *DownloadQueue
	batchSize     int
	mu            sync.RWMutex
}

// NewSmartDownloadQueue creates a new smart download queue
func NewSmartDownloadQueue(batchSize int) *SmartDownloadQueue {
	if batchSize <= 0 {
		batchSize = 5
	}

	return &SmartDownloadQueue{
		PriorityDownloadQueue: NewPriorityDownloadQueue(),
		prefetchQueue:        NewDownloadQueue(),
		batchSize:            batchSize,
	}
}

// AddPrefetch adds a download to the prefetch queue
func (sdq *SmartDownloadQueue) AddPrefetch(download *Download) {
	sdq.prefetchQueue.Add(download)
}

// GetBatch retrieves a batch of downloads
func (sdq *SmartDownloadQueue) GetBatch() []*Download {
	sdq.mu.Lock()
	defer sdq.mu.Unlock()

	var batch []*Download

	// First, try to get from main queue
	for i := 0; i < sdq.batchSize; i++ {
		download := sdq.PriorityDownloadQueue.GetNonBlocking()
		if download == nil {
			break
		}
		batch = append(batch, download)
	}

	// If main queue is empty, try prefetch queue
	if len(batch) == 0 {
		for i := 0; i < sdq.batchSize; i++ {
			download := sdq.prefetchQueue.GetNonBlocking()
			if download == nil {
				break
			}
			batch = append(batch, download)
		}
	}

	return batch
}

// GetPrefetchQueueSize returns the size of the prefetch queue
func (sdq *SmartDownloadQueue) GetPrefetchQueueSize() int {
	sdq.mu.RLock()
	defer sdq.mu.RUnlock()
	return sdq.prefetchQueue.Size()
}

// IsPrefetchQueueEmpty returns true if the prefetch queue is empty
func (sdq *SmartDownloadQueue) IsPrefetchQueueEmpty() bool {
	sdq.mu.RLock()
	defer sdq.mu.RUnlock()
	return sdq.prefetchQueue.IsEmpty()
}